using System;
using Microsoft.SPOT;

namespace G2Tech.SharedContext
{
    /// <summary>
    /// This is the Shared Context Manager Class
    /// </summary>
    public class ContextManager
    {
        #region Private
        
        //this contains the list of all registered comunication channels
        private System.Collections.Hashtable _communicationChannels = new System.Collections.Hashtable();

        //this contains the most updated version of the context items
        private System.Collections.Hashtable _contextItems = new System.Collections.Hashtable();

        //this is my address!
        private DeviceAddress _myAddress = null;

        #endregion

        #region Constructor
        
        /// <summary>
        /// Create a ContextManager
        /// </summary>
        /// <param name="myAddress"></param>
        public ContextManager(DeviceAddress myAddress, System.Collections.ArrayList communicationChannels = null)
        {
            _myAddress = myAddress;

            if (communicationChannels != null)
            {
                foreach (var channel in communicationChannels)
                {
                    if (channel is ICommunicationChannel)
                    {
                        _communicationChannels.Add((channel as ICommunicationChannel).Name, channel);
                    }
                }
            }

            startupEngine();
        }

        #endregion
        
        #region Public API 

        /// <summary>
        /// Update a Context Item value
        /// </summary>
        /// <param name="contextName"></param>
        /// <param name="contextValue"></param>
        public void UpdateContextItem(string contextName, object contextValue)
        {
            ContextItem item = new ContextItem();
                        
            item.LastUpdate = DateTime.UtcNow;
            item.Source = _myAddress;
            item.Value = contextValue;
            item.Name = contextName;

            realayItemToChannels(item, _communicationChannels.Values);

            if ( OnItemUpdate != null)
            {
                OnItemUpdate(item);
            }
        }

        /// <summary>
        /// Get a context Item Value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="contextName"></param>
        /// <returns></returns>
        public ContextItem GetContextItem(string contextName)
        {
            if (_contextItems.Contains(contextName))
            {
                return (ContextItem)_contextItems[contextName];
            }

            return null;
        }

        /// <summary>
        /// On Item Update
        /// </summary>
        public event ContextManagerDelegates.ContextManagerItemUpdateEventHandler OnItemUpdate;

        #endregion

        #region Comunication Channel managing
        
        /// <summary>
        /// Register the Comunication Channel
        /// </summary>
        /// <param name="channel"></param>
        public void AddComunicationChannel(ICommunicationChannel channel)
        {
            if (!_communicationChannels.Contains(channel.Name))
            {
                _communicationChannels.Add((channel as ICommunicationChannel).Name, channel);
            }

            startupEngine();
        } 

        #endregion


        /// <summary>
        /// Startup the Comunication engine
        /// </summary>
        private void startupEngine()
        {
            foreach(var channel in _communicationChannels.Values)
            {
                var channelBase = channel as CommunicationChannelBase;

                if (!channelBase.IsStarted)
                { 
                    //register for channel events...
                    channelBase.OnItemReceived += new CommunicationChannelDelegates.CommunicationChannelItemReceivedEventHandler(channelBase_OnItemReceived);

                    //and start the 
                    channelBase.Start();
                }
            }
            
        }

        /// <summary>
        /// notify a list of channel of a new updated item value!!!
        /// </summary>
        /// <param name="item"></param>
        /// <param name="channels"></param>
        private void realayItemToChannels(ContextItem item, System.Collections.ICollection channels)
        {
            if (item != null && channels != null && channels.Count > 0)
            {
                foreach (var channel in channels)
                {
                    //send the item to the channel!
                    (channel as ICommunicationChannel).SendItem(item);
                }
            }
        }

        /// <summary>
        /// channel item received
        /// </summary>
        /// <param name="incomingChannel"></param>
        /// <param name="item"></param>
        void channelBase_OnItemReceived(ICommunicationChannel incomingChannel, ContextItem item)
        {
            //ehy an item was updated by a channel!!!

            //first of all take the current local value
            var currentLocalValue = _contextItems.Contains(item.Name) ? _contextItems[item.Name] as ContextItem : null as ContextItem;

            if (item.LastUpdate > currentLocalValue.LastUpdate)
            {
                //good i received from a channel a newer value!

                //OK UPDATE LOCAL VALUE!!!
                _contextItems[item.Name] = item;

                if (OnItemUpdate != null)
                {
                    OnItemUpdate(item);
                }
            }

            //share this value to the other channel!!!
            var otherChannels = calculateChannelsToRelay(item, incomingChannel);

            realayItemToChannels(item, otherChannels);
        }

        /// <summary>
        /// get a list of channels except the one provided 
        /// </summary>
        /// <param name="incomingChannel"></param>
        /// <returns></returns>
        private System.Collections.ArrayList calculateChannelsToRelay(ContextItem item, ICommunicationChannel incomingChannel)
        {
            System.Collections.ArrayList otherChannels = new System.Collections.ArrayList();

            foreach (var otherChannel in _communicationChannels.Values)
            { 
                if ((otherChannel as ICommunicationChannel).Name != incomingChannel.Name)
                {
                    otherChannels.Add(otherChannel);
                }
            }

            return otherChannels;
        } 
    }
}
